home *** CD-ROM | disk | FTP | other *** search
- #!/usr/bin/python
- """
- The module provides a client to the PackageKit DBus interface. It allows to
- perform basic package manipulation tasks in a cross distribution way, e.g.
- to search for packages, install packages or codecs.
- """
- # Copyright (C) 2008 Canonical Ltd.
- # Copyright (C) 2008 Aidan Skinner <aidan@skinner.me.uk>
- # Copyright (C) 2008 Martin Pitt <martin.pitt@ubuntu.com>
- # Copyright (C) 2008 Tim Lauridsen <timlau@fedoraproject.org>
- # Copyright (C) 2008-2009 Sebastian Heinlein <devel@glatzor.de>
- #
- # Licensed under the GNU General Public License Version 2
- #
- # This program is free software; you can redistribute it and/or modify
- # it under the terms of the GNU General Public License as published by
- # the Free Software Foundation; either version 2 of the License, or
- # (at your option) any later version.
- #
- # This program is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU General Public License for more details.
- #
- # You should have received a copy of the GNU General Public License
- # along with this program; if not, write to the Free Software
- # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
-
- import locale
- import os
- import weakref
-
- import dbus
- import dbus.glib
- import dbus.mainloop.glib
- dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
- import gobject
-
- import enums
- import debconf
- import errors
-
- class AptMessage:
- """Represents a non-cirtical information or warning from the daemon"""
- def __init__(self, enum, details):
- self.enum = enum
- self.details = details
-
- class AptDaemonError(Exception):
- """Wrapper for a ErrorCode message"""
- def __init__(self, code, details=None):
- Exception.__init__(self)
- self.code = code
- self.details = details
-
- def __str__(self):
- return "%s: %s" % (self.code, self.details)
-
-
- class MemoizedTransaction(type):
-
- """Metaclass to cache transactions."""
-
- cache = weakref.WeakValueDictionary()
-
- def __call__(cls, *args):
- tid = args[0]
- try:
- return cls.cache[tid]
- except KeyError:
- cls.cache[tid] = value = \
- super(MemoizedTransaction, cls).__call__(*args)
- return value
-
-
- class MemoizedMixIn(MemoizedTransaction, gobject.GObjectMeta):
- pass
-
-
- class AptTransaction(gobject.GObject):
-
- """Represents an aptdaemon transaction. It allows asynchronous and
- synchronous processing.
- """
-
- __metaclass__ = MemoizedMixIn
-
- __gsignals__ = {"finished" : (gobject.SIGNAL_RUN_FIRST,
- gobject.TYPE_NONE,
- (gobject.TYPE_INT,)),
- "error" : (gobject.SIGNAL_RUN_FIRST,
- gobject.TYPE_NONE,
- (gobject.TYPE_INT, gobject.TYPE_STRING)),
- "role" : (gobject.SIGNAL_RUN_FIRST,
- gobject.TYPE_NONE,
- (gobject.TYPE_INT,)),
- "allow-terminal" : (gobject.SIGNAL_RUN_FIRST,
- gobject.TYPE_NONE,
- (gobject.TYPE_BOOLEAN,)),
- "allow-cancel" : (gobject.SIGNAL_RUN_FIRST,
- gobject.TYPE_NONE,
- (gobject.TYPE_BOOLEAN,)),
- "status" : (gobject.SIGNAL_RUN_FIRST,
- gobject.TYPE_NONE,
- (gobject.TYPE_INT,)),
- "status-details" : (gobject.SIGNAL_RUN_FIRST,
- gobject.TYPE_NONE,
- (gobject.TYPE_STRING,)),
- "progress" : (gobject.SIGNAL_RUN_FIRST,
- gobject.TYPE_NONE,
- (gobject.TYPE_INT,)),
- "progress-details" : (gobject.SIGNAL_RUN_FIRST,
- gobject.TYPE_NONE,
- (gobject.TYPE_INT, gobject.TYPE_INT,
- gobject.TYPE_INT, gobject.TYPE_INT,
- gobject.TYPE_INT, gobject.TYPE_INT)),
- "medium-required" : (gobject.SIGNAL_RUN_FIRST,
- gobject.TYPE_NONE,
- (gobject.TYPE_STRING,
- gobject.TYPE_STRING)),
- "config-file-prompt" : (gobject.SIGNAL_RUN_FIRST,
- gobject.TYPE_NONE,
- (gobject.TYPE_STRING,
- gobject.TYPE_STRING)),
- }
-
- def __init__(self, tid, bus=None):
- gobject.GObject.__init__(self)
- self.tid = tid
- self._role = enums.ROLE_UNSET
- self._error_code = None
- self._error_details = None
- self._exit = None
- self._allow_cancel = False
- self._allow_terminal = False
- self._required_medium = None
- self._config_file_prompt = None
- self._status = None
- self._status_details = ""
- self._progress = 0
- self._method = None
- self._exit_handler = None
- self._messages = []
- self._method = None
- self._args = []
- self._debconf_helper = None
- # Connect the signal handlers to the DBus iface
- if not bus:
- bus = dbus.SystemBus()
- self._proxy = bus.get_object("org.debian.apt", tid)
- self._iface = dbus.Interface(self._proxy, "org.debian.apt.transaction")
- # Watch for a crashed daemon which orphaned the dbus object
- self._owner_watcher = bus.watch_name_owner("org.debian.apt",
- self._on_name_owner_changed)
- # main signals
- self._signal_matchers = []
- for sig, cb in [('Finished', self._on_finished),
- ('ErrorCode', self._on_error),
- ('Message', self._on_message),
- ('Role', self._on_role),
- ('Status', self._on_status),
- ('StatusDetails', self._on_status_details),
- ('Progress', self._on_progress),
- ('ProgressDetails', self._on_progress_details),
- ('MediumRequired', self._on_medium_required),
- ('AllowTerminal', self._on_allow_terminal),
- ('ConfigFilePrompt', self._on_config_file_prompt),
- ('AllowCancel', self._on_allow_cancel)]:
- matcher = self._iface.connect_to_signal(sig, cb, utf8_strings=True)
- self._signal_matchers.append(matcher)
- self._main_loop = gobject.MainLoop()
-
- def _on_name_owner_changed(self, connection):
- """Fail the transaction if the daemon died."""
- if connection == "" and not self._exit:
- self._on_error(enums.ERROR_DAEMON_DIED,
- "It seems that the daemon died.")
- self._on_allow_cancel(False)
- self._on_allow_terminal(False)
- self._on_finished(enums.EXIT_FAILED)
-
- def _on_allow_terminal(self, allow_terminal):
- """Callback for AllowTerminal signal"""
- self._allow_terminal = allow_terminal
- self.emit("allow-terminal", allow_terminal)
-
- def _on_allow_cancel(self, allow_cancel):
- """Callback for AllowCancel signal"""
- self._allow_cancel = allow_cancel
- self.emit("allow-cancel", allow_cancel)
-
- def _on_role(self, role):
- """Callback for Role signal"""
- self._role = role
- self.emit("role", role)
-
- def _on_status(self, status):
- """Callback for Status signal"""
- self._status = status
- self.emit("status", status)
-
- def _on_status_details(self, details):
- """Callback for StatusDetails signal"""
- self._status_details = details
- self.emit("status-details", details)
-
- def _on_progress(self, percent):
- """Callback for Progress signal"""
- self._progress = percent
- self.emit("progress", percent)
-
- def _on_config_file_prompt(self, old, new):
- """Callback for ConfigFilePrompt signal"""
- self._config_file_prompt = (old, new)
- self.emit("config-file-prompt", old, new)
-
- def _on_medium_required(self, label, drive):
- """Callback for MediumRequired signal"""
- self._required_medium = (label, drive)
- self.emit("medium-required", label, drive)
-
- def _on_progress_details(self, items_done, items_total, bytes_done,
- bytes_total, speed, eta):
- """Callback for ProgressDetails signal"""
- self.emit("progress-details", items_done, items_total, bytes_done,
- bytes_total, speed, eta)
-
- def _on_message(self, enum, details):
- """Callback for Message signal"""
- msg = AptMessage(enum, details)
- self._messages.append(msg)
-
- def _on_error(self, code, details):
- """Callback for ErrorCode signal"""
- #FIXME: Store an exception instance
- self._error_code = code
- self._error_details = details
- self.emit("error", code, details)
-
- def _on_finished(self, enum):
- """Callback for Finished signal"""
- self._exit = enum
- self.emit("finished", enum)
- self._owner_watcher.cancel()
- if self._debconf_helper:
- self._debconf_helper.stop()
- self.detach()
- self._main_loop.quit()
- if self._exit_handler is not None:
- self._exit_handler(self, enum)
-
- def set_method(self, method, *args):
- """Setup the method of the DBus interface which should be handled"""
- self._method = self._iface.get_dbus_method(method)
- self._args = args
-
- def run(self, block=False, error_handler=None, reply_handler=None):
- """Start processing the transaction.
-
- Keyword arguemnts:
- block -- start the transaction by a sync call and return after it
- is done.
- This argument will be deprecated in the future.
- You should prefer an async call by specfying the
- error_handler and reply_handler.
- error_handler -- callback whill will called in the case of an error
- reply_handler -- callback which will called if the transaction is done
- """
- if error_handler and reply_handler:
- self._method(*self._args, timeout=250,
- error_handler=error_handler,
- reply_handler=reply_handler)
- return
- # avoid blocking the user interface
- context = gobject.main_context_default()
- while context.pending():
- context.iteration()
- self._method(*self._args, timeout=250)
- if self._exit_handler is None or block == True:
- self._main_loop.run()
- if self._exit_handler is None and self._error_code is not None:
- raise AptDaemonError(self._error_code, self._error_details)
- return self._exit
-
- def get_status(self):
- """Get the status of the transactioan."""
- return self._status
-
- def get_exit_state(self):
- """Return the finished status"""
- return self._exit
-
- def get_error(self):
- """Returns the AptDaemonError of a failed transaction."""
- if self._error_code is not None:
- return AptDaemonError(self._error_code, self._error_details)
- else:
- return None
-
- def cancel(self, reply_handler=None, error_handler=None):
- """Cancel the running transaction."""
- self._iface.Cancel(reply_handler=reply_handler,
- error_handler=error_handler)
-
- def set_http_proxy(self, proxy,
- reply_handler=None, error_handler=None):
- """Set the http_proxy property"""
- self._proxy.Set("org.debian.apt.transaction",
- "HttpProxy", proxy,
- dbus_interface=dbus.PROPERTIES_IFACE,
- reply_handler=reply_handler,
- error_handler=error_handler)
-
- def set_allow_unauthenticated(self, allow_unauthenticated,
- reply_handler=None, error_handler=None):
- """Set the allow_unauthenticated property"""
- self._proxy.Set("org.debian.apt.transaction",
- "AllowUnauthenticated", allow_unauthenticated,
- dbus_interface=dbus.PROPERTIES_IFACE,
- reply_handler=reply_handler,
- error_handler=error_handler)
-
- def set_debconf_frontend(self, frontend, reply_handler=None,
- error_handler=None):
- """Set the debconf socket """
- self._debconf_helper = debconf.DebconfProxy(frontend)
- self._proxy.Set("org.debian.apt.transaction", "DebconfSocket",
- self._debconf_helper.socket_path,
- dbus_interface=dbus.PROPERTIES_IFACE,
- reply_handler=reply_handler,
- error_handler=error_handler)
- self._debconf_helper.start()
-
- def set_terminal(self, ttyname, reply_handler=None, error_handler=None):
- """Set the controlling terminal."""
- self._proxy.Set("org.debian.apt.transaction", "Terminal", ttyname,
- dbus_interface=dbus.PROPERTIES_IFACE,
- reply_handler=reply_handler,
- error_handler=error_handler)
-
- def attach(self, reply_handler=None, error_handler=None):
- """Request the current state from the transaction."""
- self._iface.Attach(reply_handler=reply_handler,
- error_handler=error_handler)
-
- def detach(self):
- """Stop monitoring the progress of the transaction."""
- for sig in self._signal_matchers:
- sig.remove()
- del sig
-
- def set_locale(self, locale, reply_handler=None, error_handler=None):
- """Set the language."""
- self._proxy.Set("org.debian.apt.transaction", "Locale", locale,
- dbus_interface=dbus.PROPERTIES_IFACE,
- reply_handler=reply_handler,
- error_handler=error_handler)
-
- def provide_medium(self, medium, reply_handler=None, error_handler=None):
- """Continue a paused transaction which waited for a medium to install
- from.
- """
- self._iface.ProvideMedium(medium, reply_handler=reply_handler,
- error_handler=error_handler)
-
- def config_file_prompt_answer(self, config, answer, reply_handler=None,
- error_handler=None):
- """Continue a paused transaction that waits for config file prompt
- """
- self._iface.ConfigFilePromptAnswer(config, answer,
- reply_handler=reply_handler,
- error_handler=error_handler)
-
- class AptClient:
- """Aptdaemon client wrapper class"""
- def __init__(self):
- """
- Initialize the client.
- """
- self.apt_control = None
- self.bus = dbus.SystemBus()
- self._locale = "%s.%s" % locale.getdefaultlocale()
- self.terminal = None
-
- def get_trusted_vendor_keys(self, exit_handler=None):
- """Return the list of trusted vendors."""
- daemon = get_aptdaemon()
- #FIXME: Should be async
- keys = daemon.GetTrustedVendorKeys()
- return keys
-
- def upgrade_system(self, safe_mode=True, exit_handler=None):
- """Upgrade system."""
- return self._run_transaction("UpgradeSystem", [safe_mode],
- exit_handler)
-
- def install_packages(self, package_names, exit_handler=None):
- """Install packages by name."""
- return self._run_transaction("InstallPackages", [package_names],
- exit_handler)
-
- def add_repository(self, type, uri, dist, comps=[""], comment="",
- sourcesfile=""):
- """Add repository to the sources list.
-
- Keyword arguments:
- type -- the type of the entry (deb, deb-src)
- uri -- the main repository uri (e.g. http://archive.ubuntu.com/ubuntu)
- dist -- the distribution to use (e.g. karmic, "/")
- comps -- a (possible empty) list of components (main, restricted)
- comment -- an (optional) comment
- sourcesfile -- an (optinal) filename in sources.list.d
- """
- daemon = get_aptdaemon()
- # dbus can not deal with empty lists and will error
- if comps == []:
- comps = [""]
- #FIXME: Should support async call
- return daemon.AddRepository(type, uri, dist, comps, comment,
- sourcesfile)
-
- def add_vendor_key_from_file(self, path, exit_handler=None):
- """Add signing key from the given file to the trusted vendors."""
- return self._run_transaction("AddVendorKeyFromFile", [path],
- exit_handler)
-
- def remove_vendor_key(self, fingerprint, exit_handler=None):
- """Remove key from the list of trusted vendors."""
- return self._run_transaction("RemoveVendorKey", [fingerprint],
- exit_handler)
-
- def install_file(self, path, exit_handler=None):
- """Install local package file."""
- return self._run_transaction("InstallFile", [path], exit_handler)
-
- def upgrade_packages(self, package_names, exit_handler=None):
- """Upgrade packages by name."""
- return self._run_transaction("UpgradePackages", [package_names],
- exit_handler)
-
- def remove_packages(self, package_names, exit_handler=None):
- """Remove packages by name."""
- return self._run_transaction("RemovePackages", [package_names],
- exit_handler)
-
- def commit_packages(self, install, reinstall, remove, purge, upgrade,
- exit_handler=None):
- """Perform complex packages changes."""
- return self._run_transaction("CommitPackages",
- [install, reinstall, remove, purge,
- upgrade],
- exit_handler)
-
- def update_cache(self, exit_handler=None):
- """Update repository information."""
- return self._run_transaction("UpdateCache", [], exit_handler)
-
- def _run_transaction(self, method_name, args, exit_handler):
- """Run the given method in a new transaction."""
- try:
- tid = self.apt_control.RequestTransaction()
- except (AttributeError, dbus.DBusException), e:
- if self.apt_control == None or (hasattr(e, "_dbus_error_name") and \
- e._dbus_error_name == "org.freedesktop.DBus.Error." \
- "ServiceUnknown"):
- # first initialization (lazy) or timeout
- self.apt_control = dbus.Interface(
- self.bus.get_object("org.debian.apt", "/org/debian/apt",
- False),
- "org.debian.apt")
- tid = self.apt_control.RequestTransaction()
- else:
- raise
- trans = AptTransaction(tid, self.bus)
- if self._locale is not None:
- trans.set_locale(self._locale)
- if self.terminal is not None:
- trans.set_terminal(self.terminal)
- trans.set_method(method_name, *args)
- if exit_handler is not None:
- trans._exit_handler = exit_handler
- return trans
- else:
- return trans.run()
-
- def get_transaction(tid, bus=None):
- """Return an AptTransaction instance connected to the given transaction.
-
- Keyword arguments:
- tid -- the identifier of the transaction
- bus -- the D-Bus connection that should be used (defaults to the system bus)
- """
- if not bus:
- bus = dbus.SystemBus()
- trans = AptTransaction(tid, bus)
- trans.attach()
- return trans
-
- def get_aptdaemon(bus=None):
- """Return the aptdaemon D-Bus interface.
-
- Keyword argument:
- bus -- the D-Bus connection that should be used (defaults to the system bus)
- """
- if not bus:
- bus = dbus.SystemBus()
- return dbus.Interface(bus.get_object("org.debian.apt",
- "/org/debian/apt",
- False),
- "org.debian.apt")
-
- # vim:ts=4:sw=4:et
-